<?php

namespace Search\Search360;

use Search\SearchTool;
use Search\SearchResult;
use DOMDocument;
use DOMNode;
use DOMElement;

final class Search360 implements SearchTool
{
    /**
     * @param string $keywords
     * @return SearchResult[]
     * @throws Search360Exception
     */
    public function search(string $keywords): array
    {
        $allResults = [];
        for ($i = 1; $i <= 3; $i++) {
            $url = $this->getQueryUrl($keywords, $i);
            $allResults = array_merge($allResults, $this->htmlTextToResult($this->doQuery($url)));
        }
        return $allResults;
    }

    private function getQueryUrl(string $keywords, int $pageNo = 1): string
    {
        if (1 == $pageNo) {
            return 'https://www.so.com/s?ie=utf-8&q=' . urlencode($keywords);
        }
        return 'https://www.so.com/s?ie=utf-8&q=' . urlencode($keywords) . '&pn=' . ($pageNo - 1);
    }

    private function doQuery(string $url): string
    {
        $handle = curl_init();
        if (false === $handle) {
            throw new Search360Exception('`curl_init` fail!');
        }
        
        if (false === curl_setopt_array($handle, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76',
                CURLOPT_URL => $url,
                CURLOPT_SSL_VERIFYPEER => false,
            ])
        ) {
            throw new Search360Exception('`curl_setopt_array` fail!');
        }

        $result = curl_exec($handle);
        if (false === $result) {
            throw new Search360Exception('`curl_exec` fail: ' . curl_error($handle));
        }

        curl_close($handle);
        return $result;
    }

    private function htmlTextToResult(string $htmlText): array
    {
        $doc = new DOMDocument();
        @$doc->loadHTML($htmlText);
        
        $list = $doc->getElementsByTagName('li');

        $result = [];
        foreach ($list as $item) {
            try {
                $result[] = $this->findResultFromDOM($item);
            } catch (NoSearchResultException $e) {
            }
        }
        return $result;
    }

    private function findResultFromDOM(DOMNode $item): SearchResult
    {
        if (in_array('res-list', $this->getClasses($item))) {
            try {
                $searchResult = $this->getSearchResultFromDOM($item);
            } catch (Search360Exception $e) {
                throw new NoSearchResultException($e->getMessage());
            }
            return $searchResult;
        }
        throw new NoSearchResultException('No SearchResult');
    }

    private function getSearchResultFromDOM(DOMElement $item): SearchResult
    {
        $titleList = $item->getElementsByTagName('h3');
        $title = '';
        if (0 != $titleList->length) {
            $title = trim(htmlspecialchars_decode($titleList->item(0)->textContent));
        }
        $richContentList = $item->getElementsByTagName('div');
        $content = '';
        foreach ($richContentList as $richContent) {
            if (in_array('res-rich', $this->getClasses($richContent))) {
                $content .= trim(htmlspecialchars_decode($richContent->textContent)) . PHP_EOL;
            }
        }
        return new Search360Result($title, trim($content));
    }

    private function getClasses(DOMNode $item): array
    {
        $results = [];
        if ($item instanceof DOMElement) {
            $results = explode(' ', $item->getAttribute('class'));
        }
        return $results;
    }
}
